﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Globalization;

namespace Bolseye
{
    public enum TickerStatus
    {
        Added, 
        Repeated,
        Error
    }

    class ParsingSummary
    {
        public TickerStatus Status;
        public string tMessage = "";
        public string Html = "";
        public string URL = "";

        public int SimboloID { get; set; }

        public string Simbolo { get; set; }
    }




    public class ParserMaster
    {
        int maxWorkers = 10;

        int _m_busyWorkers = 0;

        int _m_sleeptime = 2000;

        int _m_secsThreshold = 60;

        int _m_secsHealthCheck = 120;       // SEGUNDOS QUE TRANSCURREN ENTRE CADA VEZ QUE SE CONTROLA LA SALUD DE LOS SIMBOLOS.

        int _m_secsHealthThreshold = 120;   // SEGUNDOS QUE PUEDEN PASAR SIN QUE UN SIMBOLO SE ACTUALICE EXITOSAMENTE.


        static object _lockPad = new object();

        List<int> _m_simbolosEnProceso = new List<int>();

        //int _m_secsThresholdHealthy = 120;  /// SECONDS TO CHECK FOR 

        private delegate ParsingSummary ParserDelegate(Simbolo s);

        public ParserMaster()
        {
            DateTime lastRefresh = DateTime.MinValue;

            List<Simbolo> simbolos = new List<Simbolo>();

            bool firstTimeAround = true;

            while (true)
            {
                if (DateTime.UtcNow.Subtract(lastRefresh).TotalSeconds > _m_secsThreshold)
                {
                    Console.WriteLine("Actualizando lista de simbolos...");

                    var db = Support.GetDB();

                    var simbolosObj = from s in db.Simbolos
                                      select s;

                    simbolos = simbolosObj.ToList();

                    if (!firstTimeAround)
                        ControlarSimbolos(simbolos);

                    firstTimeAround = false;

                    lastRefresh = DateTime.UtcNow;
                }

                Parse(simbolos);

            }
        }

        private void ControlarSimbolos(List<Simbolo> simbolos)
        {
            simbolos.ForEach(s =>
            {
                if (DateTime.UtcNow.Subtract(s.UltimaActualizacion).TotalSeconds > _m_secsHealthThreshold)
                {
                    string msg = @"Han pasado mas de {0} segundos y el simbolo {1} no se esta mostrando como actualizado. Revisar logs !";
                    msg = string.Format(msg, _m_secsHealthThreshold, s.ID);

                    Console.WriteLine(msg);

                    int delay = 300;
                    for (int n = 0; n < 5; n++)
                    {
                        System.Media.SystemSounds.Hand.Play();
                        System.Threading.Thread.Sleep(delay);
                        System.Media.SystemSounds.Exclamation.Play();
                        System.Threading.Thread.Sleep(delay);
                        System.Media.SystemSounds.Asterisk.Play();
                        System.Threading.Thread.Sleep(delay);
                    }

                    Support.GrabarRegistro(1964, s.ID, "SIMBOLO NO SE ACTUALIZA", "", "", msg);
                }
            });
        }



        private void Parse(List<Simbolo> simbolos)
        {
            simbolos.ForEach(s =>
            {
                while (_m_busyWorkers >= maxWorkers)
                {
                    Console.WriteLine("Demasiados trabajadores ocupados: {0} de un max de {1}",
                        _m_busyWorkers, maxWorkers);
                    System.Threading.Thread.Sleep(_m_sleeptime);
                }

                lock (_lockPad)
                {
                    if (_m_simbolosEnProceso.Contains(s.ID))
                    {
                        //Console.WriteLine("Simbolo [" + s.Simb + "] todavia en proceso de parsing...");
                        return;
                    }
                    _m_simbolosEnProceso.Add(s.ID);
                }

                Console.WriteLine("Ejecutando parsing de simbolo >> " + s.Simb);

                var parser = new ParserDelegate(Parsing.ParseSomething);

                var callBack = new AsyncCallback(ParsingComplete);

                parser.BeginInvoke(s, callBack, parser);

                _m_busyWorkers++;
            });
        }


        private void ParsingComplete(IAsyncResult result)
        {
            var r = result.AsyncState as ParserDelegate;

            ParsingSummary summary = null;

            string errr = null;

            try
            {
                summary = r.EndInvoke(result);
            }
            catch (Exception x)
            {
                errr = x.ToString();
            }

            if (summary == null)
            {
                Support.GrabarRegistro(68751, "EXCEPTION NO IDENTIFICADA", "", "", errr);
            }
            else if (summary.Status == TickerStatus.Error)
            {
                Support.GrabarRegistro(52473, summary.SimboloID, summary.Status.ToString(), summary.Html, summary.URL, summary.tMessage);
            }
            else
            {
                Support.MarcarSimboloActualizado(summary.SimboloID);
            }

            Console.WriteLine("Se completo el parsing del simbolo {0} - {1}", summary.Simbolo, summary.Status);

            lock (_lockPad)
                _m_simbolosEnProceso.Remove(summary.SimboloID);

            _m_busyWorkers--;
        }

    }




    public class Parsing
    {
        public static void ParseSimbolos()
        {
            var db = Support.GetDB();

            foreach (var s in db.Simbolos)
            {
                Console.Write("Parsing simbolo [" + s.Simb + "]... ");
                var resultado = ParseSomething(s);
                Console.WriteLine(resultado);
            }

            Console.WriteLine("TERMINATED... Key to finish");
            Console.ReadKey();
        }


        internal static ParsingSummary ParseSomething(Simbolo simbObj)
        {
            ParsingSummary summary = new ParsingSummary();
            summary.URL = simbObj.LinkCotizaciones;
            summary.Simbolo = simbObj.Simb;
            summary.SimboloID = simbObj.ID;

            string html = "";

            try
            {
                HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(simbObj.LinkCotizaciones);

                var resp = (HttpWebResponse)req.GetResponse();

                using (var sr = new StreamReader(resp.GetResponseStream()))
                {
                    html = sr.ReadToEnd();
                }
            }
            catch (Exception x)
            {
                summary.Status = TickerStatus.Error;
                summary.tMessage = x.ToString();
                return summary;
            }

            summary.Html = html;


            //string cue1 = "28/09/2012 16:59:04";
            //string cueCierre = "82";
            //string cueMin = "81,2";
            //string cueMax = "83,1";
            //string cueAper = "82,35";

            //string xPath1 = FindSomething(html, cue1);
            //string xPath2 = FindSomething(html, cueCierre);
            //string xPath3 = FindSomething(html, cueMin);
            //string xPath4 = FindSomething(html, cueMax);
            //string xPath5 = FindSomething(html, cueAper);

            HtmlAgilityPack.HtmlDocument d = new HtmlAgilityPack.HtmlDocument();
            d.LoadHtml(html);


            string xPath_fecha = "/html[1]/body[1]/div[1]/div[3]/div[5]/table[1]/tr[1]/td[1]/table[1]/tr[1]/td[1]/table[1]/tr[2]/td[1]/table[1]/tr[3]/td[4]";
            string xPath_cierre = "/html[1]/body[1]/div[1]/div[3]/div[5]/table[1]/tr[1]/td[1]/table[1]/tr[1]/td[1]/table[1]/tr[2]/td[1]/table[1]/tr[3]/td[8]/b[1]";
            string xPath_min = "/html[1]/body[1]/div[1]/div[3]/div[5]/table[1]/tr[1]/td[1]/table[1]/tr[1]/td[1]/table[1]/tr[2]/td[1]/table[1]/tr[3]/td[16]";
            string xPath_max = "/html[1]/body[1]/div[1]/div[3]/div[5]/table[1]/tr[1]/td[1]/table[1]/tr[1]/td[1]/table[1]/tr[2]/td[1]/table[1]/tr[3]/td[18]";
            string xPath_aper = "/html[1]/body[1]/div[1]/div[3]/div[5]/table[1]/tr[1]/td[1]/table[1]/tr[1]/td[1]/table[1]/tr[2]/td[1]/table[1]/tr[3]/td[12]";


            var nodoFecha = d.DocumentNode.SelectSingleNode(xPath_fecha);
            var nodoCierre = d.DocumentNode.SelectSingleNode(xPath_cierre);
            var nodoAper = d.DocumentNode.SelectSingleNode(xPath_aper);
            var nodoMin = d.DocumentNode.SelectSingleNode(xPath_min);
            var nodoMax = d.DocumentNode.SelectSingleNode(xPath_max);


            if (nodoFecha == null || nodoCierre == null
                || nodoAper == null || nodoMin == null || nodoMax == null)
            {
                summary.Status = TickerStatus.Error;
                summary.tMessage = "No se encontro alguno de los nodos necesarios con la informacion de las cotizaciones";
                return summary;
            }


            double cotiMin;
            double cotiMax;
            double cotiAper;
            double cotiCierre;

            if (!double.TryParse(nodoMin.InnerHtml, out cotiMin) ||
                !double.TryParse(nodoMax.InnerHtml, out cotiMax) ||
                !double.TryParse(nodoCierre.InnerHtml, out cotiCierre) ||
                !double.TryParse(nodoAper.InnerHtml, out cotiAper))
            {
                summary.Status = TickerStatus.Error;
                summary.tMessage = "No se pudo convertir en DOBLE alguna de las cotizaciones recibidas.";
                return summary;
            }

            string strFecha = nodoFecha.InnerHtml;
            DateTime fecha;
            if (!DateTime.TryParse(strFecha, out fecha))
            {
                summary.Status = TickerStatus.Error;
                summary.tMessage = "No pudimos convertir el valor de la fecha";
                return summary;
            }


            /// ANTES DE INSERTAR UN NUEVO REGISTRO, VERIFIQUEMOS QUE EL ULTIMO REGISTRO NO SEA IDENTICO...
            /// 
            var db = Support.GetDB();

            var lastTicker = (
                            from t in db.Tickers
                            where t.Simbolo == simbObj.ID
                            orderby t.ID descending
                            select t).FirstOrDefault();


            if (lastTicker != null)
            {
                if (lastTicker.Minimo == cotiMin
                    && lastTicker.Maximo == cotiMax
                    && lastTicker.Cierre == cotiCierre
                    && lastTicker.Apertura == cotiAper
                    && lastTicker.Hora == fecha)
                {
                    /// ESTE TICKER ES IDENTICO AL ULTIMO, RECHAZANDOLO.
                    /// 
                    summary.Status = TickerStatus.Repeated;
                    return summary;
                }
            }



            Bolseye.Ticker newTicker = new Ticker();
            newTicker.Creado = DateTime.UtcNow;
            newTicker.Hora = fecha;
            newTicker.Precio = cotiCierre;
            newTicker.PrecioCompra = cotiCierre;
            newTicker.PrecioVenta = cotiCierre;

            newTicker.Apertura = cotiAper;
            newTicker.Cierre = cotiCierre;
            newTicker.Maximo = cotiMax;
            newTicker.Minimo = cotiMin;

            newTicker.Simbolo = simbObj.ID;

            db.Tickers.AddObject(newTicker);

            int aff = db.SaveChanges();

            summary.Status = TickerStatus.Added;

            return summary;
        }


        public static string FindSomething(string html, string text)
        {
            HtmlAgilityPack.HtmlDocument d = new HtmlAgilityPack.HtmlDocument();
            d.LoadHtml(html);

            return Recurse(d.DocumentNode, text);
        }


        private static string Recurse(HtmlAgilityPack.HtmlNode node, string text)
        {
            string modText = (text + "").Trim();

            string t = (node.InnerHtml + "").Trim();
            if (string.Equals(t, modText, StringComparison.OrdinalIgnoreCase))
            {
                bool gotcha = true;
                string path = node.XPath;
                return path;
            }

            foreach (var n in node.ChildNodes)
            {
                string rval = Recurse(n, modText);
                if (!string.IsNullOrEmpty(rval)) return rval;
            }

            return null;
        }

    }
}
